QuickView version |
Technote 1075 |
October 1996 |
Apple Guide has many advantages over other help systems but also has several drawbacks. One is that it is not easy to keep track of what the user has already done. This becomes a particular problem if you are writing a tutorial. For example, your tutorial might have 10 different lessons which the user may need to complete over several days. How can the user be sure that he or she has gone back to the point they left the tutorial and not skipped stages? One way to do this is to store variables within an application.
This Technote also discusses using helper applications with Apple Guide.
The following enclosed code sample demonstrates several important techniques that can be used in the creation of a guide file. It demonstrates how to write a background-only helper application and how to use it to store and access variables which are used by the guide. In addition, it shows you how to add a coach mark handler. To download it, just click on the icon below:
Apple Guide Variables Code Sample Folder (305K)
Storing the variables within your application is often the easiest place to put them. You need to do several things:
The examples below just store an array of longs and initialize everything to 0 each time the application is run. For simplicity sake, the codes only going to set them to 0 or 1 and test whether they are 0 or non 0. An unregistered suite of Apple Events 'Vals' is going to be used. This is for demonstration purposes only and you should use your own ID's. The Apple Guide accesses variables 1 to 3, however, as arrays in 'C' start from 0, indexes from the guide are mapped from 1-3 to 0-2.
You need to create storage for variables and then initialize them. You may want to store and recall these from a preference file or clear them each time the application is run. Listing 1 contains storage for 3 variables and a context check reference. It also shows how to initializes them and install the handlers.
Listing 1 Initializing the guide variables and handlers
// Maximum number of values that can be stored by app #define kMaxAGEntries 3 // Storage for values long gAGValues[kMaxAGEntries]; // Storage for context check references AGContextRefNum contextRefNum; void ClearAllAGValues() { long count; for (count=0;count < kMaxAGEntries;count++) gAGValues[count] = 0; } OSErr InitAGVars() { OSErr theErr = noErr; short count; // Clear all the values stored by the application ClearAllAGValues(); // Install context check handler. MyContextCallBack will get called when a // context check of 'Vals' is queried by the Guide theErr = AGInstallContextHandler( NewContextReplyProc(MyContextCallBack), 'Vals', 0, &contextRefNum ); if (theErr) DebugStr("\pGot an Err - AGInstallContextHandler "); // set up the dispatch table for the Apple Events theErr = AEInstallEventHandler( 'Vals', 'Cler', NewAEEventHandlerProc(ClearAGValues), 0, false) ; theErr = AEInstallEventHandler( 'Vals', 'Clr1', NewAEEventHandlerProc(ClearAGValues1), 0, false) ; theErr = AEInstallEventHandler( 'Vals', 'Clr2', NewAEEventHandlerProc(ClearAGValues2), 0, false) ; theErr = AEInstallEventHandler( 'Vals', 'Clr3', NewAEEventHandlerProc(ClearAGValues3), 0, false) ; theErr = AEInstallEventHandler( 'Vals', 'Set1', NewAEEventHandlerProc(SetAGValues1), 0, false) ; theErr = AEInstallEventHandler( 'Vals', 'Set2', NewAEEventHandlerProc(SetAGValues2), 0, false) ; theErr = AEInstallEventHandler( 'Vals', 'Set3', NewAEEventHandlerProc(SetAGValues3), 0, false) ; return theErr; }
Setting and clearing of variables is done by Apple Events. Each event has a specific ID and no passed parameters. You will probably need to make these obvious by their ID. For instance, to set and clear value 1, use the event ID's 'Set1' and 'Clr1' . The source for the handlers can be found in Listing 1.
Listing 2 Setting and clearing a variable
pascal OSErr SetAGValues1(const AppleEvent *message, const AppleEvent *reply, long refcon) { gAGValues[0] = 1; return( noErr ); } pascal OSErr ClearAGValues1(const AppleEvent *message, const AppleEvent *reply, long refcon) { gAGValues[0] = 0; return( noErr ); }
For ease, you may want to clear all the variables. This is done by event ID 'Cler'.
Listing 3 Clearing all the variables
pascal OSErr ClearAGValues(const AppleEvent *message, const AppleEvent *reply, long refcon) { ClearAllAGValues(); return( noErr ); }
Checking the state of the variables is handled by Apple Guide context checks. There are mechanisms to pass data from a guide file into a context check; therefore, you only need one check which can be used to interrogate all the values. In this case, two longs are passed in the pInputData parameter. The easiest way to access them is to create a structure.
Listing 4 Passed data
struct PassedAGParamType { long command; long index; }; typedef struct PassedAGParamType PassedAGParamType, *PassedAGParamPtr, **PassedAGParamHdl;
If command contains a 0, then a check is made to see if the value is 0. If it contains a 1, then the check sees it if the value is non-0. Index is used to define which variable to check.
Listing 5 Context check
pascal OSErr MyContextCallBack( Ptr pInputData, Size inputDataSize, Ptr *ppOutputData, Size *pOutputDataSize, AGAppInfoHdl hAppInfo ) { OSErr theErr = noErr; Boolean checkResult = false; // Check the index is within range if (((PassedAGParamPtr)pInputData)->index <= kMaxAGEntries) // Work out whether we need to return true or false if (((PassedAGParamPtr)pInputData)->command == 1) checkResult = gAGValues[ ((PassedAGParamPtr)pInputData)->index - 1 ] != 0; else checkResult = gAGValues[ ((PassedAGParamPtr)pInputData)->index - 1 ] == 0; // Set up the return values ppOutputData and pOutputDataSize theErr = MySetContextResult ( checkResult, ppOutputData, pOutputDataSize ); return theErr; }
Once the result of the check has been determined,
MySetContextResult()
is used to fill in the ppOutputData
and pOutputDataSize. The result is returned in this way because it is
part of a far wider mechanism. MySetContextResult
creates a pointer and moves the value in result into to it. Next, it
returns the newly created pointer and the size of the data within it.
Apple Guide is responsible for deallocating this pointer.
Listing 6 Setting the context check result
OSErr MySetContextResult( Boolean result, Ptr *outMessage, Size *outSize ) { Ptr pOut; Size theSize = sizeof ( Boolean ); OSErr theErr = noErr; pOut = NewPtr (theSize); if ( pOut ) { BlockMove( (void *)&result, pOut, theSize ); *outSize = theSize; *outMessage = pOut; } else theErr = MemError(); return theErr; }
Before your application quits, you should remove the context check handlers using AGRemoveContextHandler. You also may want to save the variables into a preference file.
Listing 7 Removing the context check
void RemoveAGStuff() { OSErr theErr; theErr = AGRemoveContextHandler( &contextRefNum ); }
Once the necessary calls have been added to the application, it's time to make changes to the guide's source files as follows:
To make use of the events defined in the application, you need to define the events in the guide.
Listing 8 Defining the events<Define Event> "ClearValues", 'AgHp', 'Vals', 'Cler' <Define Event> "SetFlag1", 'AgHp', 'Vals', 'Set1' <Define Event> "ClearFlag1", 'AgHp', 'Vals', 'Clr1'ClearValues sends the Apple Event 'Cler' of event class 'Vals' to the application with the creator type 'AgHp'. This is defined in the application to clear all variables to 0.
As previously mentioned, context checks have a mechanism for passing data. They are defined in the additional parameters of the <DCC> command. In this case, if we want to check that a variable has been set,we would use the command:
Listing 9 Defining the context checks
<DCC> "IsFlagSet", 'Vals', 'AgHp', LONG:1, LONG
'Vals' is the context check in the application with the identifier 'AgHp'. The first additional parameter is a long with a default value of one. The second is a long which will need a value entered each time the check is used. These two parameters correspond to the data structure PassedAGParamType described above; therefore, to check to see if variable one has been set:
<If> (IsFlagSet(1))If you want to be more specific, for example, a check to see if lesson one has been completed could be:
<DCC> "IsLesson1Done", 'Vals', 'AgHp', LONG:1, LONG:1 <If> (IsLesson1Done())
Note that because both parameters have defaults, you don't need to enter a parameter when the check is used.
To mark a sequence completed, use an <On Panel Hide> command with a suitable event to set the variable.
Listing 10 Marking a sequence complete
<Define Panel> "Lesson 1 - last panel" Panel text.... <On Panel Hide> SetFlag1() <End Panel>
To skip sequences, create a containing sequence that will take you through your tutorial. You'll probably want to put an intro and outro panel into the sequence. To decide whether to show a lesson sequence, use the <If> command as shown below. This will skip lessons that have already been completed.
<Define Sequence> "Tutorial" <Panel> "Tutorial Intro" <If> NOT Flag1Set() <Jump Sequence> "Lesson 1" <End If> <If> NOT Flag2Set() <Jump Sequence> "Lesson 2" <End If> <If> NOT Flag3Set() <Jump Sequence> "Lesson 3" <End If> <Panel> "Tutorial Outro" <End Sequence>
It's not always possible for a guide author to have access to the applications source. Therefore, it is not always possible to add the features described above to an application. Also, some applications use Apple Events but are not necessarily scripted. Setting up these events may be very difficult or impossible from Apple Guide. In these cases, it's possible to write a small background-only application which can be used to store variables and from simple Apple Events create and trigger far more sophisticated events. This small application could be used to store variables and handle the code described above.
To trigger it, you need to add a compiled Apple Script to the application.
Listing 11 Apple Script to trigger helper application
tell application "AGHelperApp" launch end tell
When you compile the script, make sure the helper application is in the same folder as the script. Also, if you change the creator type of the helper, you will need to recompile the script.
You will probably need to start the helper from the first panel of the tutorial sequence. In the example below, the compiled script is called StartHelperApp.
Listing 12 Starting the helper application from within the guide
<Define Panel> "Tutorial Intro" Tutorial Intro text... <On Panel Create> DoAppleScript( "StartHelperApp" ) <End Panel>
Guide files and tutorials can seriously add to the usability of your product. Keeping track of how far the user has gone through your tutorial will help the user learn your product. The techniques in this Technote will help you to implement this. Use them!
Special thanks to Jason Hodges-Harris.